﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Collections.Generic
{
    /// <summary>
    /// Provides a set of static methods extending the <see cref="IEnumerable&lt;T&gt;"/> class.
    /// </summary>
    public static class IEnumerableExtensions
    {
        /// <summary>
        /// Performs the specified action on each element of the <see cref="IEnumerable&lt;T&gt;"/>.
        /// </summary>        
        /// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
        /// <param name="source">The source of the elements.</param>
        /// <param name="action">The <see cref="Action&lt;T&gt;"/> delegate to perform on each element of the <see cref="IEnumerable&lt;T&gt;"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="action"/> is null.</exception>
        public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
        {
            if (source == null || action == null)
            {
                throw new ArgumentNullException(source == null ? "action" : "source");
            }

            foreach (var item in source)
            {
                action(item);
            }
        }
        
        /// <summary>
        /// Filters a sequence of recursive values based on a predicate.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
        /// <param name="source">An <see cref="System.Collections.Generic.IEnumerable&lt;T&gt;"/> to filter recursively.</param>
        /// <param name="predicate">A function to test each element for a condition.</param>
        /// <param name="selector">A recursive function to apply to each element.</param>
        /// <returns>An <see cref="System.Collections.Generic.IEnumerable&lt;T&gt;"/> that contains elements from the input sequence that satisfy the condition.</returns>
        public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source
            , Func<TSource, bool> predicate
            , Func<TSource, IEnumerable<TSource>> selector)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            
            if (predicate == null)
                throw new ArgumentNullException("predicate");

            if (selector == null)
                throw new ArgumentNullException("selector");
            
            // call an inner method to avoid deferred argument check
            return InternalWhere<TSource>(source, predicate, selector);
        }

        internal static IEnumerable<TSource> InternalWhere<TSource>(this IEnumerable<TSource> source
            , Func<TSource, bool> predicate
            , Func<TSource, IEnumerable<TSource>> selector)
        {
            foreach (var item in source)
            {
                if (predicate(item))
                    yield return item;

                var innerSource = selector(item);
                if (innerSource != null)
                {
                    foreach (var innerItem in Where(innerSource, predicate, selector))
                    {
                        if (predicate(innerItem))
                            yield return innerItem;
                    }
                }
            }

        }
    }
}
